import com.sun.j3d.utils.universe.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.media.j3d.*;
import javax.swing.*;

public class PongDemo {
	// Physics updates per second (approximate).
	private static final int UPDATE_RATE = 30;
	// Number of full iterations of the collision detection and resolution system.
	private static final int COLLISION_ITERATIONS = 4;
	// Width of the extent in meters.
	private static final float EXTENT_WIDTH = 20;

	// Normal boundaries that cause collisions
	private final HalfSpace[] boundaries;
	// Special boundaries that may result in object migration
	private final HalfSpace leftBoundary;
	private final HalfSpace rightBoundary;
	private final LinkedList<PhysicsObject> objects;
	private Peer peer;
	private BranchGroup scene;
	JFrame appFrame;
	JTextField scoreField;
	boolean gameOver = false;
	Random random = new Random();
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new PongDemo().createAndShowGUI();
			}
		});
	}

	public PongDemo() {
		peer = new Peer();
		peer.setup();
		
		final int CIRCLES = 1;
		final int TRIANGLES = 1;
		final int SQUARES = 1;
		
		boundaries = new HalfSpace[] {new HalfSpace(-EXTENT_WIDTH/2, -EXTENT_WIDTH/2, 0, 1),
		                              new HalfSpace(EXTENT_WIDTH/2, EXTENT_WIDTH/2, 0, -1)};
		leftBoundary = new HalfSpace(-EXTENT_WIDTH/2, -EXTENT_WIDTH/2, 1, 0);
		rightBoundary = new HalfSpace(EXTENT_WIDTH/2, EXTENT_WIDTH/2, -1, 0);
		objects = new LinkedList<PhysicsObject>();
		for (int i = 0; i < CIRCLES; i++)
			objects.add(new Circle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
			                       -4.0f, 3.0f,
			                       0, 0, EXTENT_WIDTH * .03f, null, null));
		
		for (int i = 0; i < TRIANGLES; i++)
			objects.add(new Triangle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
					-4.0f, 3.0f,
			                                (float)(2 * Math.PI * Math.random()), 0, EXTENT_WIDTH * .1f, null, true));
		
		for (int i = 0; i < SQUARES; i++)
			objects.add(new Square(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
					-4.0f, 3.0f,
			                                (float)(2 * Math.PI * Math.random()), 0, EXTENT_WIDTH * .1f, null));
		
	}
	

	private void createAndShowGUI() {
		// Fix for background flickering on some platforms
		System.setProperty("sun.awt.noerasebackground", "true");

		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
		final Canvas3D canvas3D = new Canvas3D(config);
		SimpleUniverse simpleU = new SimpleUniverse(canvas3D);
		simpleU.getViewingPlatform().setNominalViewingTransform();
		simpleU.getViewer().getView().setSceneAntialiasingEnable(true);

		// Add a scaling transform that resizes the virtual world to fit
		// within the standard view frustum.
		BranchGroup trueScene = new BranchGroup();
		TransformGroup worldScaleTG = new TransformGroup();
		Transform3D t3D = new Transform3D();
		t3D.setScale(.9 / EXTENT_WIDTH);
		worldScaleTG.setTransform(t3D);
		trueScene.addChild(worldScaleTG);
		scene = new BranchGroup();
		scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		worldScaleTG.addChild(scene);
		
		final TransformGroup extentTransform = new TransformGroup();
		extentTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		extentTransform.addChild(createExtent());
		scene.addChild(extentTransform);
		for (PhysicsObject o : objects)
			scene.addChild(o.BG);
		simpleU.addBranchGraph(trueScene);

		appFrame = new JFrame("Pong");
		appFrame.setLayout(new BorderLayout());
		scoreField = new JTextField("Points: " + peer.getScore());
		scoreField.setEditable(false);
		scoreField.setHorizontalAlignment(JTextField.CENTER);
		Font font = new Font("Verdana", Font.BOLD, 40);
		scoreField.setFont(font);
		scoreField.setForeground(Color.BLUE);
		appFrame.add(scoreField, BorderLayout.NORTH);
		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.add(canvas3D);
		appFrame.pack();
		if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH))
			appFrame.setExtendedState(appFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
		
		new Timer(1000 / UPDATE_RATE, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				
				canvas3D.stopRenderer();
				tick();
				canvas3D.startRenderer();
			}
			
		}).start();
		
		appFrame.setVisible(true);
	}
	
	private void tick() {
		List<PhysicsObject> received = peer.getReceived();
		
		if (received != null)
			for (PhysicsObject original : received) {
				PhysicsObject o;
				int nextObject = random.nextInt(3);
				if(nextObject == 0) {
					o = new Circle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
		                       (float)(Math.random() - .5) * EXTENT_WIDTH/2, (float)(Math.random() - .5) * EXTENT_WIDTH/2,
		                       0, 0, EXTENT_WIDTH * .03f, null, null);
					o.velocity = original.velocity;
					o.position = original.position;
				} else if(nextObject == 1) {
					o = new Triangle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
							(float)(Math.random() - .5) * EXTENT_WIDTH/2, (float)(Math.random() - .5) * EXTENT_WIDTH/2,
					                                (float)(2 * Math.PI * Math.random()), 0, EXTENT_WIDTH * .1f, null, true);
					o.velocity = original.velocity;
					o.position = original.position;
					
				} else {
					o = new Square(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
							(float)(Math.random() - .5) * EXTENT_WIDTH/2, (float)(Math.random() - .5) * EXTENT_WIDTH/2,
					                                (float)(2 * Math.PI * Math.random()), 0, EXTENT_WIDTH * .1f, null);
					o.velocity = original.velocity;
					o.position = original.position;
				}
				
				
				o.position.x = o.position.x;
				o.velocity.x = -o.velocity.x;
				o.updateTransformGroup();
				scene.addChild(o.BG);
				objects.add(o);
			}
		for (PhysicsObject o : objects) {
			// Hard-coded gravity
//			o.forceAccumulator.y = -10 * o.mass;
			o.updateState(1f / UPDATE_RATE);
		}
		for (int i = 0; i < COLLISION_ITERATIONS; i++) {
			Iterator<PhysicsObject> itr = objects.iterator();
			while (itr.hasNext()) {
				PhysicsObject o = itr.next();
				
				
				for (HalfSpace hs : boundaries)
					CollisionHandler.checkAndResolveCollision(hs, o);
				for (PhysicsObject o2 : objects)
					CollisionHandler.checkAndResolveCollision(o2, o);
				CollisionInfo ci = CollisionHandler.getCollisionInfo(leftBoundary, o);
				if (ci != null) {
					
					
					if (peer.sendPhysicsObject(o, random.nextInt(1024))) {
						o.resetScored();
						o.BG.detach();
						itr.remove();
						continue;
					} else
						CollisionHandler.resolveCollision(leftBoundary, o, ci);
				} 
				ci = CollisionHandler.getCollisionInfo(rightBoundary, o);
				if (ci != null) {
					
					   
					    o.position.x = 0.0f;
					    o.position.y = 0.0f;
					    o.velocity.x = -10.0f;
					    o.velocity.y = 0.0f;
					    if(peer.getScore() == 9) {
					    	peer.disconnectFromNetwork();
					    	gameOver = true; 
					    	scoreField.setText("GAME OVER!");
					    	
					    } else if (!o.scored()) {
					    	o.score();
					        peer.incrementScore();
					        scoreField.setText("Points: " + peer.getScore());
					    }
					    
				}
			}
		}
		for (PhysicsObject o : objects) {
			o.updateTransformGroup();
			// Clear the object's force accumulator.
			o.forceAccumulator.x = o.forceAccumulator.y = 0;
		}
	}

	private static Node createExtent() {
		float[] coordinates = {-EXTENT_WIDTH/2, -EXTENT_WIDTH/2, 0,
		                       EXTENT_WIDTH/2, -EXTENT_WIDTH/2, 0,
		                       EXTENT_WIDTH/2, EXTENT_WIDTH/2, 0,
		                       -EXTENT_WIDTH/2, EXTENT_WIDTH/2, 0,
		                       -EXTENT_WIDTH/2, -EXTENT_WIDTH/2, 0};
		LineStripArray geometry = new LineStripArray(5, GeometryArray.COORDINATES, new int[] {5});
		
		geometry.setCoordinates(0, coordinates);
		Shape3D shape = new Shape3D(geometry);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(new ColoringAttributes(1f, 1f, 1f, ColoringAttributes.FASTEST));
		shape.setAppearance(appearance);
		
		return shape;
	}
	
	
}